/**
 * 
 */
package org.swing.utility.jai.util;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.renderable.ParameterBlock;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Locale;
import java.util.Stack;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.media.jai.Interpolation;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.RenderedOp;
import javax.media.jai.operator.TransposeDescriptor;
import javax.media.jai.operator.TransposeType;

import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.xpath.XPath;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import com.sun.media.jai.codec.SeekableStream;

/**
 * @author  Le Quynh Nhu
 *
 */
public class ImageProcessing extends jaiGUI {

	/**
	 * Internal class for reading EXIF information
	 * 
	 * @author sberczi
	 * 
	 */
	static class EXIFReader {

		private static String getMetadata(Node root) {
			return getMetadata(root, 0);
		}

		private static String getMetadata(Node node, int level) {
			String retval = "";
			// print open tag of element
			retval += indent(level);
			retval += "<" + node.getNodeName();
			NamedNodeMap map = node.getAttributes();
			if (map != null) {

				// print attribute values
				int length = map.getLength();
				for (int i = 0; i < length; i++) {
					Node attr = map.item(i);
					retval += " " + attr.getNodeName() + "=\""
							+ attr.getNodeValue() + "\"";
				}
			}

			Node child = node.getFirstChild();
			if (child == null) {
				// no children, so close element and return
				retval += "/>\n";
				return retval;
			}

			// children, so close current tag
			retval += ">\n";
			while (child != null) {
				// print children recursively
				retval += getMetadata(child, level + 1);
				child = child.getNextSibling();
			}

			// print close tag of element
			retval += indent(level);
			retval += "</" + node.getNodeName() + ">\n";
			return retval;
		}

		private static String indent(int level) {
			String retval = "";
			for (int i = 0; i < level; i++) {
				retval += "    ";
			}
			return retval;
		}

		public static String readMetadata(String fileName, String readerName) {
			String retval = "";
			try {

				File file = new File(fileName);
				ImageInputStream iis = ImageIO.createImageInputStream(file);
				Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);

				if (readers.hasNext()) {

					ImageReader reader = readers.next();
					reader.setInput(iis, true);
					IIOMetadata metadata = reader.getImageMetadata(0);

					String[] names = metadata.getMetadataFormatNames();
					int length = names.length;
					for (int i = 0; i < length; i++) {
						if (names[i].equals(readerName)) {
							retval = getMetadata(metadata.getAsTree(names[i]));
						}
					}
				}
			} catch (Exception e) {
			}
			return retval;
		}

	}

	/**
	 * 
	 */
	public static enum inOutMethodType {
		JAIstream, JAIfileLoadSave
	}

	static class ParameterException extends Exception {
		private static final long serialVersionUID = -6674310053033650364L;

		public ParameterException() {
		}

		public ParameterException(String parameter) {
			super("Parameter " + parameter + " needs a second value too.");
		}
	}

	static class ParameterIncorrectException extends Exception {

		private static final long serialVersionUID = -5524885785198137075L;

		public ParameterIncorrectException() {
		}

		public ParameterIncorrectException(String parameter) {
			super("Parameter '" + parameter + "' can't be parsed.");
		}
	}

	public static enum resizeMethodType {
		JAIscale, JAIaffine, AWT, JAIscaleMultiple, nConvert, GraphicsHD
	}

	private static final long serialVersionUID = 728478065541612461L;
	private static inOutMethodType INOUTMETHOD = inOutMethodType.JAIfileLoadSave;

	static private boolean ENABLEGUI = false;

	/**
	 * Rotating option: flag for indicating: Rotating enabled according to the
	 * EXIF information.
	 */
	private static boolean ENABLEROTATE_EXIF = true;

	/**
	 * Rotating option: flag for indicating: Rotating enabled according to
	 * display.
	 */
	private static boolean ENABLEROTATE_DISPLAY = true;

	/**
	 * Rotating option: rotate direction (left/right).
	 */
	private static TransposeType rotateMode = TransposeDescriptor.ROTATE_90;

	/**
	 * Resizing option: flag for indicating: resize enabled
	 */
	private static boolean ENABLERESIZE = true;

	/**
	 * Resizing option: Pan&Scan enabled
	 */
	private static boolean ENABLEPANANDSCAN = false;

	/**
	 * Resizing option: Output image size
	 */
	static Dimension DISPLAYSIZE = new Dimension(240, 320);

	/**
	 * Resizing option: flag for indicating: enlarging enabled
	 */
	static boolean ENABLEENLARGEIMAGE = false;

	/**
	 * Resizing option: method of resizing.
	 */
	private static resizeMethodType RESIZEMETHOD = resizeMethodType.GraphicsHD;

	/**
	 * Resizing option: internal variable for storing batch string for nConvert
	 */
	private static StringBuffer nConvertBatch;

	/**
	 * Resizing option: path of nConvert.exe
	 */
	private static File nConvert = new File(
			"M:\\Programok\\JPG\\nconvert\\NCONVERT.EXE");

	// Default:Rotate left

	/**
	 * Resizing option: buffer size for nConvert
	 */
	private static int nConvertBatchSize = 5;
	static boolean ENABLEOVERWRITE = true;

	static int interPolationMode = Interpolation.INTERP_BICUBIC_2;
	static String EXIFreaderName = "com_sun_media_imageio_plugins_tiff_image_1.0";

	static String EXIFXpath = "/" + EXIFreaderName
			+ "//TIFFField[@number=\"274\"]//@value";

	static ArrayList<File> sourceDirs = new ArrayList<File>();
	static File destDirRoot = null;
	static RenderingHints qualityHints;

	private static jaiGUI getGui() {
		if (ENABLEGUI && gui == null) {
			gui = new jaiGUI();
		}
		return gui;
	}

	public static File getSourceDir(int i) {
		return sourceDirs.get(i);
	}

	public static boolean isEnablePanAndScan() {
		return ENABLEPANANDSCAN;
	}

	public static void setEnableGui(boolean enablegui) {
		ENABLEGUI = enablegui;
		getGui();
	}

	public static void setEnablePanAndScan(boolean enablePanAndScan) {
		ENABLEPANANDSCAN = enablePanAndScan;
	}

	private PlanarImage image;

	private Document exifInfo;

	static jaiGUI gui;

	static String title;

	public static void main(String[] args) {

		args = new String []{"3.png"};
		ArrayList<File> source = new ArrayList<File>();
		ImageProcessing imageProcessing = new ImageProcessing();

		Stack<String> argStack = new Stack<String>();
		for (int i = args.length - 1; i >= 0; i--) {
			argStack.add(args[i]);
		}

		String param, paramNext = null;
		while (!argStack.isEmpty()) {
			param = argStack.pop();
			if (param.startsWith("-")) {
				param = param.substring(1); // cut -
				if (!argStack.isEmpty()) {
					paramNext = argStack.pop();
					if (paramNext.startsWith("-") || argStack.isEmpty()) {
						argStack.push(paramNext);
						paramNext = null;
					}
				}
			} else {
				// Source
				paramNext = param;
				param = "source";
			}

			try {
				if (param.equalsIgnoreCase("enablegui")) {
					setEnableGui(true);
				} else if (param.equalsIgnoreCase("displaySize")) {

					if (paramNext != null) {
						int width, height;
						String[] dim = paramNext.split("x");
						if (dim.length == 2) {
							width = Integer.parseInt(dim[0]);
							height = Integer.parseInt(dim[1]);

							DISPLAYSIZE.width = width;
							DISPLAYSIZE.height = height;
						}
					} else {
						throw new ParameterException(param);
					}
				} else if (param.equalsIgnoreCase("source")) {
					ImageProcessing.sourceDirs.add(sourceDirs.size(), new File(
							paramNext));
				} else if (param.equalsIgnoreCase("destination")) {
					destDirRoot = new File(paramNext);
				} else if (param.equalsIgnoreCase("noRotateDiplay")) {
					ENABLEROTATE_DISPLAY = false;
				} else if (param.equalsIgnoreCase("enablePanAndScan")) {
					ENABLEPANANDSCAN = true;
				} else if (param.equalsIgnoreCase("noExifRotate")) {
					ENABLEROTATE_EXIF = false;
				} else if (param.equalsIgnoreCase("noResize")) {
					ENABLERESIZE = false;
				} else if (param.equalsIgnoreCase("resizeMethod")) {
					if (paramNext.equalsIgnoreCase("JAIscale")) {
						RESIZEMETHOD = resizeMethodType.JAIscale;
					} else if (paramNext.equalsIgnoreCase("JAIaffine")) {
						RESIZEMETHOD = resizeMethodType.JAIaffine;
					} else if (paramNext.equalsIgnoreCase("JAIscaleMultiple")) {
						RESIZEMETHOD = resizeMethodType.JAIscaleMultiple;
					} else if (paramNext.equalsIgnoreCase("nConvert")) {
						RESIZEMETHOD = resizeMethodType.nConvert;
					} else {
						RESIZEMETHOD = resizeMethodType.nConvert;
					}
				} else if (param.equalsIgnoreCase("enableEnlarge")) {
					ENABLEENLARGEIMAGE = true;
				} else if (param.equalsIgnoreCase("enableOverwrite")) {
					ENABLEOVERWRITE = true;
				} else if (param.equalsIgnoreCase("nconvert")) {
					nConvert = new File(paramNext);
				} else if (param.equalsIgnoreCase("help")
						|| param.equalsIgnoreCase("h")) {
					imageProcessing.usage();
					System.exit(0);
				} else if (false) {
					throw new ParameterIncorrectException(param);

				} else {
				}
			} catch (ParameterIncorrectException e) {
				System.err.println(e.getMessage());
				imageProcessing.usage();

			} catch (ParameterException e) {
				System.err.println(e.getMessage());
				imageProcessing.usage();
			}

		}
		imageProcessing.run();

		if (gui != null) {
			gui.display(null);
		}
		imageProcessing = null;
		System.exit(0);
	}

	/**
	 * Properties of the file to be processed: file label
	 */
	private String label;

	/**
	 * Properties of the file to be processed: file
	 */
	private File inFile;

	/**
	 * Properties of the file to be processed: destination
	 */
	private File destination;

	private File sourceRootDir;

	public ImageProcessing() {

		if (ENABLEGUI) {
			gui = new jaiGUI();
		}
		qualityHints = new RenderingHints(RenderingHints.KEY_RENDERING,
				RenderingHints.VALUE_RENDER_QUALITY);
		qualityHints.put(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		qualityHints.put(RenderingHints.KEY_COLOR_RENDERING,
				RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		qualityHints.put(RenderingHints.KEY_DITHERING,
				RenderingHints.VALUE_DITHER_ENABLE);
		qualityHints.put(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BICUBIC);

	}

	public ImageProcessing(String fileName) {
		this();
		load(fileName);
		this.inFile = new File(fileName);
	}

	public ImageProcessing(String fileName, String p_label) {
		this(fileName);
		this.label = p_label;
	}

	public void addSourceDir(File file) {
		sourceDirs.add(file);
	}

	private PlanarImage crop(PlanarImage pImage, int topLeftmx, int topLeftmy,
			int roiWidth, int roiHeight) {

		System.out.println(" cropping (" + topLeftmx + ", " + topLeftmy + ", "
				+ roiWidth + ", " + roiHeight + ")");

		if (topLeftmx + roiWidth > pImage.getWidth()) {
			roiWidth = pImage.getWidth() - topLeftmx;
			System.out
					.println("  The rectangular crop area would be outside the image - trim width to "
							+ roiWidth);
		}
		if (topLeftmy + roiHeight > pImage.getHeight()) {
			roiHeight = pImage.getHeight() - topLeftmy;
			System.out
					.println("  The rectangular crop area would be outside the image - trim height to "
							+ roiHeight);
		}

		ParameterBlock pbm = new ParameterBlock();
		pbm = new ParameterBlock();
		pbm.addSource(pImage);
		pbm.add((float) topLeftmx);
		pbm.add((float) topLeftmy);
		pbm.add((float) roiWidth);
		pbm.add((float) roiHeight);
		return JAI.create("crop", pbm);
	}

	/**
	 * It makes the rotation of a picture, according its EXIF data.
	 */
	private void exifOrientate() {
		if (!ENABLEROTATE_EXIF) {
			return;
		}
		int jpegExifOrient = 0;
		try {
			jpegExifOrient = Integer.valueOf(getExifValue(EXIFXpath));
		} catch (NumberFormatException e) {
			// tag cannot be read.
		} catch (Exception e) {
			// e.printStackTrace();
		}
		switch (jpegExifOrient) {
		case 1:
			// no action
			break;
		case 2:
			System.out.println(" Based EXIF:");
			transpose(TransposeDescriptor.FLIP_HORIZONTAL);
			break;
		case 3:
			System.out.println(" Based EXIF:");
			transpose(TransposeDescriptor.ROTATE_180);
			break;
		case 4:
			System.out.println(" Based EXIF:");
			transpose(TransposeDescriptor.FLIP_VERTICAL);
			break;
		case 5:
			System.out.println(" Based EXIF:");
			transpose(TransposeDescriptor.FLIP_ANTIDIAGONAL);
			// transpose
			break;
		case 6:
			System.out.println(" Based EXIF:");
			transpose(TransposeDescriptor.ROTATE_90);
			// rotate 90
			break;
		case 7:
			System.out.println(" Based EXIF:");
			transpose(TransposeDescriptor.FLIP_DIAGONAL);
			// transverse
			break;
		case 8:
			System.out.println(" Based EXIF:");
			transpose(TransposeDescriptor.ROTATE_270);
			// rotate 270
			break;

		default:
			// no action
			break;
		}
	}

	/**
	 * Generates the destination name for the inputFile.
	 * 
	 * @param sourceDirs
	 * @return
	 */
	private String getDestPath() {

		String ret;
		String destPat = inFile.getPath();
		String sdp = sourceRootDir.getPath();
		String ddp = destDirRoot.getPath();
		ret = ddp + "\\" + destPat.replace(sdp + "\\", "");
		return ret;
	}

	public Dimension getDisplaySize() {
		return DISPLAYSIZE;
	}

	/**
	 * It gets a specified EXIF parameter
	 * 
	 * @param xpathExpr
	 * @return
	 */
	private String getExifValue(String xpathExpr) {

		Attribute attr = null;
		try {
			attr = (Attribute) XPath.selectSingleNode(exifInfo, EXIFXpath);
		} catch (JDOMException e) {
			e.printStackTrace();
		}
		if (attr != null) {
			return attr.getValue();
		}
		return null;
	}

	public inOutMethodType getInOutMethod() {
		return INOUTMETHOD;
	}

	public int getInterPolationMode() {
		return interPolationMode;
	}

	public File getNConvert() {
		return nConvert;
	}

	public resizeMethodType getResizeMethod() {
		return RESIZEMETHOD;
	}

	public TransposeType getRotateMode() {
		return rotateMode;
	}

	/**
	 * Convenience method that returns a scaled instance of the provided
	 * {@code BufferedImage}.
	 * 
	 * @param img
	 *            the original image to be scaled
	 * @param targetWidth
	 *            the desired width of the scaled instance, in pixels
	 * @param targetHeight
	 *            the desired height of the scaled instance, in pixels
	 * @param hint
	 *            one of the rendering hints that corresponds to
	 *            {@code RenderingHints.KEY_INTERPOLATION} (e.g.
	 *            {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
	 *            {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
	 *            {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
	 * @param higherQuality
	 *            if true, this method will use a multi-step scaling technique
	 *            that provides higher quality than the usual one-step technique
	 *            (only useful in downscaling cases, where {@code targetWidth}
	 *            or {@code targetHeight} is smaller than the original
	 *            dimensions, and generally only when the {@code BILINEAR} hint
	 *            is specified)
	 * @return a scaled version of the original {@code BufferedImage}
	 */
	private BufferedImage getScaledInstanceGraphics(BufferedImage img,
			int targetWidth, int targetHeight, Object hint,
			boolean higherQuality) {
		int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB
				: BufferedImage.TYPE_INT_ARGB;
		BufferedImage ret = (BufferedImage) img;
		int w, h;
		if (higherQuality) {
			// Use multi-step technique: start with original size, then
			// scale down in multiple passes with drawImage()
			// until the target size is reached
			w = img.getWidth();
			h = img.getHeight();
		} else {
			// Use one-step technique: scale directly from original
			// size to target size with a single drawImage() call
			w = targetWidth;
			h = targetHeight;
		}

		do {
			if (higherQuality && w > targetWidth) {
				w /= 2;
				if (w < targetWidth) {
					w = targetWidth;
				}
			}

			if (higherQuality && h > targetHeight) {
				h /= 2;
				if (h < targetHeight) {
					h = targetHeight;
				}
			}

			BufferedImage tmp = new BufferedImage(w, h, type);
			Graphics2D g2 = tmp.createGraphics();
			g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
			g2.drawImage(ret, 0, 0, w, h, null);
			g2.dispose();

			ret = tmp;
		} while (w != targetWidth || h != targetHeight);

		return ret;
	}

	public ArrayList<File> getSourceDir() {
		return sourceDirs;
	}

	private String info() {
		String infoTxt;
		StringBuilder stringBuilder = new StringBuilder();
		// stringBuilder.append("***************\n");
		stringBuilder.append(" Info about '" + label + "'\n");
		stringBuilder.append("  Dimension: " + image.getWidth() + "x"
				+ image.getHeight() + "\n");
		// stringBuilder.append("***************\n");
		infoTxt = stringBuilder.toString();
		return infoTxt;
	}

	public boolean isEnableEnlarge() {
		return ENABLEENLARGEIMAGE;
	}

	public boolean isEnableGUI() {
		return ENABLEGUI;
	}

	public boolean isEnableOwerwrite() {
		return ENABLEOVERWRITE;
	}

	public boolean isEnableResize() {
		return ENABLERESIZE;
	}

	public boolean isEnableRotate_Display() {
		return ENABLEROTATE_DISPLAY;
	}

	public boolean isEnableRotate_EXIF() {
		return ENABLEROTATE_EXIF;
	}

	/**
	 * It loads a picture from a file.
	 * 
	 * @param fname
	 */
	private final void load(String fname) {

		inFile = new File(fname);
		label = fname;
		loadExifData();
		destination = new File(getDestPath());

		FileInputStream fis = null;
		try {

			switch (INOUTMETHOD) {
			case JAIfileLoadSave:
				RenderedOp renderedOp = JAI.create("fileload", fname);
				image = renderedOp.createInstance();
				break;
			case JAIstream:
				fis = new FileInputStream(new File(fname));
				SeekableStream s = SeekableStream.wrapInputStream(fis, true);
				image = JAI.create("stream", s);
			default:
				break;
			}

		} catch (Exception e) {
			System.err.println("File '" + fname
					+ "' cannot be read as an image.\n");
		} finally {
			try {
				//
				// Finally close the input and output stream after we've
				// finished with them.
				//
				if (fis != null) {
					fis.close();
				}
			} catch (IOException e) {
			}
		}

	}

	/**
	 * It loads the EXIF data of the picture.
	 */
	private void loadExifData() {

		String exifData = EXIFReader.readMetadata(inFile.getPath(),
				EXIFreaderName);
		if (exifData == null || exifData == "") {
			return;
		}
		File metadataFile = new File(inFile.getPath() + ".exif.xml");
		try {
			if (metadataFile.createNewFile()) {

				PrintWriter out = new PrintWriter(new BufferedWriter(
						new FileWriter(metadataFile)));
				out.print(exifData);
				out.flush();
				out.close();

				SAXBuilder builder = new SAXBuilder();

				this.exifInfo = builder.build(metadataFile);
			}
		} catch (IOException e) {
		} catch (JDOMException e) {
		} finally {
			metadataFile.delete();
		}
	}

	private void nConvAddFiletoBatch() {
		if (!nConvert.exists()) {

		}

		if (nConvertBatch == null) {
			nConvertBatch = new StringBuffer();
			// header
			String header = nConvert.getPath()
					+ " -npcd 2 -size 256x256+0 -ctype grey -corder inter -out jpeg -D -ratio -rtype lanczos -rflag decr -rflag orient -resize "
					+ (int) DISPLAYSIZE.getWidth() + " "
					+ (int) DISPLAYSIZE.getHeight() + " ";

			nConvertBatch.append(header);
		}
		nConvertBatch.append("\"" + destination.getPath() + "\" ");
	}

	public void processDir(File sourceDir) throws IOException, Exception {

//		if (sourceDir == null || sourceDir.getAbsolutePath() == "") {
//			throw new Exception("Source is not given.");
//		}
//
//		String filter = "";
//		if (!sourceDir.isDirectory()) {
//			filter = sourceDir.getName();
//			sourceDir = sourceDir.getParentFile();
//
//		}
//
//		if (!sourceDir.exists()) {
//			throw new IOException("Source doesn't exists. ("
//					+ sourceDir.getPath() + ")");
//		}
//		if (destDirRoot == null || destDirRoot.getPath().equals("")) {
//			String destDirName = sourceDir.getParentFile().getPath() + "\\"
//					+ sourceDir.getName() + "_conv";
//			destDirRoot = new File(destDirName);
//		}
//
//		if (destDirRoot.getPath().indexOf(sourceDir.getPath()) > 0) {
//			throw new Exception(
//					"Destination directory cannot be under the source directory.");
//		}
//		if (!destDirRoot.exists()) {
//			if (!destDirRoot.mkdirs()) {
//				throw new Exception("Destination directory ("
//						+ destDirRoot.getAbsolutePath()
//						+ ") cannot be created.");
//			}
//		}
//		if (!destDirRoot.isDirectory()) {
//			throw new Exception("Destination must be a directory.");
//		}
//		sourceRootDir = new File(sourceDir.getPath());
//		ArrayList<File> files = FileProcessing.listFilesFrom(sourceDir, filter,
//				true);
//		int i = 1, all = files.size();
//		if (all > 0) {
//			System.out.println("\n\nProcessing " + all + " files...");
//		}
//		for (File aFile : files) {
//			if (ENABLEGUI) {
//				gui.setTitle("" + i + "/" + all + " " + aFile.getName());
//			}
//			System.out.println("\n" + i + "/" + all);
//			i++;
//			if (processFile(sourceDir, aFile)) {
//				saveImage(destination, "JPEG");
//			}
//			if (RESIZEMETHOD == resizeMethodType.nConvert
//					&& nConvertBatch != null) {
//				if (i % nConvertBatchSize == nConvertBatchSize - 1) {
//					hu.berczi.Utils.System.SysUtil.execNoReturn(nConvertBatch
//							.toString());
//					nConvertBatch = null;
//				}
//			}
//		}
//		if (RESIZEMETHOD == resizeMethodType.nConvert && nConvertBatch != null) {
//			hu.berczi.Utils.System.SysUtil.execNoReturn(nConvertBatch
//					.toString());
//			nConvertBatch = null;
//		}
	}

	/**
	 * Processing a file
	 * 
	 * @param sourceDir
	 * @param pFile
	 * @return
	 */
	private boolean processFile(File sourceDir, File pFile) {
		boolean retval = true;
		try {
			this.load(pFile.getPath());
		} catch (Exception e) {
			System.err.println("Image couldn't be load.");
			System.err.println(e.getMessage());
		}

		System.out.println(""
				+ inFile.getPath().replace(sourceDir.getPath() + "\\", ""));
		if (destination.exists()) {
			if (ENABLEOVERWRITE) {
				if (!destination.delete()) {
					System.err.println(destination.getPath()
							+ " couldn't be deleted, skip.");
					return false;
				}
			} else {
				System.out.println(destination.getPath()
						+ " already exists, skip.");
				return false;
			}
		}

		try {
			this.rotateAndResize();
		} catch (IOException e) {
			retval = false;
			// e.printStackTrace();
		}
		return retval;
	}

	private PlanarImage recalcResize(PlanarImage pImage, resizeMethodType method) {
		if (!ENABLERESIZE) {
			return pImage;
		}

		Dimension imSize = new Dimension(image.getWidth(), image.getHeight());
		Dimension imSizeNew = new Dimension();
		float aspRatioDisplay = (float) DISPLAYSIZE.width / DISPLAYSIZE.height;
		float aspRatioImage = (float) imSize.width / imSize.height;

		// A kép arányaiban magasabb, mint a kijelzö
		if (aspRatioDisplay > aspRatioImage) {
			if (ENABLEPANANDSCAN) {
				imSizeNew.width = DISPLAYSIZE.width;
				imSizeNew.height = (int) Math.round(imSize.height
						* DISPLAYSIZE.width / imSize.height);
				// + vagas felul-alul.
			} else {
				imSizeNew.height = DISPLAYSIZE.height;
				imSizeNew.width = (int) (Math.round(aspRatioImage
						* DISPLAYSIZE.height));
			}
			// A kép arányaiban laposabb, mint a kijelzö
		} else {
			if (ENABLEPANANDSCAN) {
				imSizeNew.height = DISPLAYSIZE.height;
				imSizeNew.width = (int) (Math.round((float) DISPLAYSIZE.height
						/ (float) imSize.height * imSize.width));
				// TODO + vagas ket szelen.
			} else {
				imSizeNew.width = DISPLAYSIZE.width;
				imSizeNew.height = (int) (Math.round(DISPLAYSIZE.width
						/ aspRatioImage));

			}
		}
		boolean enlarging = ((imSizeNew.width > imSize.width) || (imSizeNew.height > imSize.height));
		if ((enlarging && ENABLEENLARGEIMAGE) || !enlarging) {
			pImage = scale(pImage, (float) imSizeNew.width / imSize.width,
					method);
			if (ENABLEPANANDSCAN) {
				boolean toBig = (pImage.getWidth() > DISPLAYSIZE.width)
						|| (pImage.getHeight() > DISPLAYSIZE.height);
				if (toBig) {
					if (aspRatioDisplay > aspRatioImage) {
						// vagas felul-alul.
						pImage = crop(
								pImage,
								0,
								(int) (pImage.getHeight() - DISPLAYSIZE.height) / 2,
								DISPLAYSIZE.width, DISPLAYSIZE.height);
					} else {
						// vagas ket szelen.
						pImage = crop(
								pImage,
								(int) (pImage.getWidth() - DISPLAYSIZE.width) / 2,
								0, DISPLAYSIZE.width, DISPLAYSIZE.height);
					}
				}
			}
		} else {
			System.out.println(" resizing skipped - enlarging not enabled");
		}
		return pImage;
	}

	private void recalcRotate2Display() {
		if (!ENABLEROTATE_DISPLAY) {
			return;
		}
		Dimension imSize = new Dimension(image.getWidth(), image.getHeight());
		float aspRatioDisplay = (float) DISPLAYSIZE.width / DISPLAYSIZE.height;
		float aspRatioImage = (float) imSize.width / imSize.height;

		if ((aspRatioDisplay < 1 && aspRatioImage > 1)
				|| (aspRatioDisplay > 1 && aspRatioImage < 1)) {
			image = this.transpose(rotateMode);
		}
	}

	private PlanarImage rotateAndResize() throws IOException {

		exifOrientate();
		recalcRotate2Display();
		image = recalcResize(image, RESIZEMETHOD);

		if (ENABLEGUI) {
			if (RESIZEMETHOD == resizeMethodType.nConvert) {
				gui.display(recalcResize(image, resizeMethodType.JAIscale));
			} else {
				gui.display(image);
			}
		}

		return image;
	}

	public void run() {
		try {

			if (RESIZEMETHOD == resizeMethodType.nConvert && !nConvert.exists()) {
				throw new Exception(nConvert.getPath() + " doesn't exists.");
			}
			if (RESIZEMETHOD == resizeMethodType.nConvert && ENABLEPANANDSCAN) {
				ENABLEPANANDSCAN = false;
				System.out
						.println("PanAndScan is disabled - not compatible with nConvert.");
			}
			if (sourceDirs != null && !sourceDirs.isEmpty()) {
				if (sourceDirs.size() > 1) {
					destDirRoot = null;
				}
				for (File aSource : sourceDirs) {
					processDir(aSource);
				}
			} else {
				System.err.println("Source not given, exiting.");
				usage();
			}
		} catch (IOException e) {
			e.printStackTrace();
			usage();
		} catch (Exception e) {
			System.err.println(e.getMessage());
			usage();
		}

	}

	/**
	 * Saves the image to the given path and filename using the given codec
	 * 
	 * @param filepath
	 *            the path and filename to save the image to.
	 * @param type
	 *            The JAI-defined codec type to save as.
	 * @throws IOException
	 */
	private void saveImage(File sfile, String type) throws IOException {

		float quality = 0.8f; // 0.5, 0.75 0.95

		sfile.getParentFile().mkdirs();

		System.out.println(" saving to " + sfile.getPath());

		switch (INOUTMETHOD) {
		case JAIfileLoadSave:
			ParameterBlock pb = new ParameterBlock();
			pb.addSource(image);
			pb.add(sfile.getPath());
			pb.add(type);

			// JAI.create("filestore", image, filepath, type, null);
			JAI.create("filestore", pb, qualityHints);
			break;
		case JAIstream:
			ImageOutputStream ios = null;
			ImageWriter writer = null;
			try {
				writer = (ImageWriter) ImageIO.getImageWritersByFormatName(
						"jpg").next();
				// we assume one is present
				ImageWriteParam iwparam = new JPEGImageWriteParam(
						Locale.getDefault());

				iwparam.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT);
				iwparam.setCompressionQuality(quality);
				ios = ImageIO.createImageOutputStream(sfile);
				writer.setOutput(ios);
				writer.write(null, new IIOImage(image, null, null), iwparam);

			} finally {
				try {
					ios.flush();
					writer.dispose();
					ios.close();
				} catch (Exception e) {
				}
			}
		default:
			break;
		}

	}

	private PlanarImage scale(PlanarImage pImage, float factor,
			resizeMethodType method) {

		float factorLimit = 0.5f;

		System.out.println(" resizing to " + (int) (factor * 100) + "% ("
				+ (int) (pImage.getWidth() * factor) + "x"
				+ (int) (pImage.getHeight() * factor) + ")");
		switch (method) {
		case JAIaffine:
			AffineTransform tr = AffineTransform.getScaleInstance(factor,
					factor);
			Interpolation interp = Interpolation.getInstance(interPolationMode);
			pImage = (PlanarImage) JAI.create("affine", pImage, tr, interp);
			break;
		case GraphicsHD:
			pImage = PlanarImage.wrapRenderedImage(getScaledInstanceGraphics(
					pImage.getAsBufferedImage(),
					(int) (pImage.getWidth() * factor),
					(int) (pImage.getHeight() * factor),
					RenderingHints.VALUE_INTERPOLATION_BILINEAR, true));
			break;
		case JAIscale:
			ParameterBlock pb = new ParameterBlock();
			pb.addSource(pImage);
			pb.add(factor);
			pb.add(factor);
			pb.add(0.0F);
			pb.add(0.0F);
			pb.add(Interpolation.getInstance(interPolationMode));
			pImage = JAI.create("scale", pb, qualityHints);
			break;
		case JAIscaleMultiple:
			if (true) {
				// first method:

				float targetW = (float) pImage.getWidth() * factor;
				while (Math.abs(targetW - pImage.getWidth()) > 3) {
					float factorEff = Math.max(targetW / pImage.getWidth(),
							factorLimit);
					ParameterBlock pbm = new ParameterBlock();
					pbm.addSource(pImage);
					pbm.add(factorEff);
					pbm.add(factorEff);
					pbm.add(0.0F);
					pbm.add(0.0F);
					pbm.add(Interpolation.getInstance(interPolationMode));
					pImage = JAI.create("scale", pbm, qualityHints);
					System.out.println("  resizing to "
							+ (int) (factorEff * 100) + "% ("
							+ pImage.getWidth() + "x" + pImage.getHeight()
							+ ")");
				}

			} else {
				// second method: equal factors.
				double a = Math.log(factor) / Math.log(factorLimit);
				int step = (int) Math.ceil(a);
				float factorEff = (float) Math.pow(factor,
						(1.0f / (float) step));

				for (int i = 0; i < step; i++) {
					ParameterBlock pbm = new ParameterBlock();
					pbm.addSource(pImage);
					pbm.add(factorEff);
					pbm.add(factorEff);
					pbm.add(0.0F);
					pbm.add(0.0F);
					pbm.add(Interpolation.getInstance(interPolationMode));

					pImage = JAI.create("scale", pbm, qualityHints);
					System.out.println("  resizing to "
							+ (int) (factorEff * 100) + "% ("
							+ pImage.getWidth() + "x" + pImage.getHeight()
							+ ")");
				}

			}
			break;
		case AWT:
			Image image = pImage.getAsBufferedImage();
			image.getScaledInstance(5, 6, java.awt.Image.SCALE_SMOOTH);
			break;
		case nConvert:
			nConvAddFiletoBatch();
			break;
		default:
			pImage = null;
			break;
		}
		return pImage;
	}

	public void setDisplaySize(Dimension displaysize) {
		DISPLAYSIZE = displaysize;
	}

	public void setEnableEnlagreImage(boolean enableenlargeimage) {
		ENABLEENLARGEIMAGE = enableenlargeimage;
	}

	public void setEnableOverwrite(boolean enableoverwrite) {
		ENABLEOVERWRITE = enableoverwrite;
	}

	public void setEnableResize(boolean enableresize) {
		ENABLERESIZE = enableresize;
	}

	public void setEnableRotate_Display(boolean enablerotate_display) {
		ENABLEROTATE_DISPLAY = enablerotate_display;
	}

	public void setEnableRotate_EXIF(boolean enablerotate_exif) {
		ENABLEROTATE_EXIF = enablerotate_exif;
	}

	public void setInOutMethod(inOutMethodType inoutmethod) {
		INOUTMETHOD = inoutmethod;
	}

	public void setInterPolationMode(int interPolationMode) {
		ImageProcessing.interPolationMode = interPolationMode;
	}

	public void setNConvert(File convert) {
		nConvert = convert;
	}

	public void setResizeMethod(resizeMethodType resizemethod) {
		RESIZEMETHOD = resizemethod;
	}

	public void setRotateMode(TransposeType rotateMode) {
		ImageProcessing.rotateMode = rotateMode;
	}

	private PlanarImage transpose(TransposeType transType) {
		ParameterBlock pb = new ParameterBlock();
		pb.addSource(image);
		pb.add(transType);
		String text = "";
		if (transType.equals(TransposeDescriptor.ROTATE_90)) {
			text = " rotating left";
		} else if (transType.equals(TransposeDescriptor.ROTATE_270)) {
			text = " rotating right";
		}
		System.out.println(text);

		return JAI.create("transpose", pb, qualityHints);
	}

	private void usage() {
		String helpText = "\n**********************************************************\n";
		helpText += " Imageprocessing: a tool to resize&rotate pictures.\n";
		helpText += "  Written by Berczi, Sandor ( berczi.sandor@gmail.com )\n\n";

		String[] p = String.valueOf(this.getClass()).split(" ")[1].split("[.]");
		helpText += " Usage:\n " + p[(p.length - 1)] + " OPTIONS SOURCE";
		helpText += "\n  OPTIONS: \n   [-enablegui]\n   [-displaySize WIDTHxHEIGHT]\n   [-destination DIRECTORY]\n   [-noRotateDiplay]\n   [-noExifRotate]\n   [-noResize]\n   [-enablePanAndScan]\n   [-resizeMethod RESIZEMETHOD]\n   [-enableEnlarge]\n   [-enableOverwrite]\n";
		helpText += "\n\n  RESIZEMETHOD: \n   nConvert | JAIscale | JAIaffine | JAIscaleMultiple";
		helpText += "\n\n  The options are case insensitiv, the order is irrelevant.";
		helpText += "\n\n  SOURCE: \n   DIRECTORY\\FILTER [DIRECTORY\\FILTER]...";
		helpText += "\n\n";

		System.out.println(helpText);
	}

}
